package main

import (
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
)

/*
	Gin内置文本模板引擎 text/template 和用于HTML文档的 html/templage
	1. 模板文件通常定义为.tmpl和.tmpl后缀，必须是UTF8编码
	2. 模板文件中使用{{}}包裹和标识需要传入的数据
	3. 传给模板的数据可以使用点号.来访问，如果是复杂类型的数据，可以通过{{.Attr}}来访问字段

	模板引擎的使用可以分为三部分
	1. 定义模板文件
		a.编写.tmpl和.tmpl文件
		b.创建模板
			funcn New(name string) *Template 创建一个名为name的模板

	2. 解析模板文件
		func (t *Template) Parse(src string) (*Template, error)
		func ParseFiles(filenames ...string) (*Template, error)
		func ParseGlob(pattern string) (*Template, error)

	3. 模板渲染
		func (t *Template) Execute(wr io.Writer, data interface{}) error
		func (t *Template) ExcuteTemplate(wr io.Writer, name string, data interface{}) error

		与ExecuteTemplate的区别在于， ExecuteTemplate可以通过第二个参数指定主模版


	模板语法：
		1. {{.}}

		2. {{.Attr}}

		3. {{ /* a comment    //注释

		4. pipeline是指产生数据的操作。比如{{.}}、{{.Name}}等

		5. $obj := {{.}}
			变量，可以在模板中声明变量，用来保存传入模板的数据或其他语句的生成结果
			在后续的代码中就可以使用该变量(obj)

		6. 移除左侧所有空白符{{-, 移除右侧所有空白符-}}, eg: {{- .Name -}}

		7. 条件判断
			{{ if pipeline }} T1 {{end}}
			{{ if pipeline }} T1 {{else}} T0 {{end}}
			{{ if pipeline }} T1 {{ else if pipeline }} T0 {{end}}

		8. 遍历range
			{{range pipeline }} T1 {{end}}  如果pipeline长度为0，不会有任何输出
			{{range pipeline }} T1 {{else}} T0 {{end}} 如果pipeline长度为0， 执行T0

		9. with
			{{ with pipeline }} T1 {{end}}
			如果pipeline为empty不产生输出，否则将dot设为pipeline的值并执行T1，不修改外面的dot
			{{ with pipeline }} T1 {{else}} T0 {{end}}
			如果pipeline为empty，不改变dot并执行T0，否则dot设为pipeline的值并执行T1

		10. 预定义函数
			执行模板时，函数从两个函数字典中查找：首先是模板函数字典，然后是全局函数字典。一般不在模板内定义函数，而是使用Funcs方法添加函数到模板里。

		11. 比较函数
			eq      如果arg1 == arg2则返回真
			ne      如果arg1 != arg2则返回真
			lt      如果arg1 < arg2则返回真
			le      如果arg1 <= arg2则返回真
			gt      如果arg1 > arg2则返回真
			ge      如果arg1 >= arg2则返回真

			{{eq arg1 arg2 arg3}}

		12. 自定义函数 {{func }}

		13. 嵌套template(见下例index和tmplDemo)

		14. 修改默认的标识符
			Go标准库的模板引擎使用的花括号{{和}}作为标识，而许多前端框架（如Vue和 AngularJS）也使用{{和}}作为标识符，
			所以当我们同时使用Go语言模板引擎和以上前端框架时就会出现冲突，
			这个时候我们需要修改标识符，修改前端的或者修改Go语言的。
			这里演示如何修改Go语言模板引擎默认的标识符：

			template.New("test").Delims("{[", "]}").ParseFiles("./t.tmpl")

		15.te


*/

// UserInfo 用户信息
type UserInfo struct {
	Name   string
	Gender string
	Age    int
}

func sayHello(w http.ResponseWriter, r *http.Request) {
	// tmpl, err := template.ParseFiles("./hello.tmpl")
	// if err != nil {
	// 	fmt.Println("create template failed, err:", err)
	// 	return
	// }

	htmlByte, err := ioutil.ReadFile("./hello.tmpl")
	if err != nil {
		fmt.Println("read html failed, err:", err)
		return
	}

	// 自定义一个夸人的模板函数
	kua := func(arg string) (string, error) {
		return arg + ", 你是个好人", nil
	}

	// 采用链式操作在Parse之前调用Funcs添加自定义的kua函数
	tmpl, err := template.New("hello").Funcs(template.FuncMap{"kua": kua}).Parse(string(htmlByte))
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}

	// 利用给定的数据渲染模板，并将结果写入w
	user := UserInfo{
		Name:   "老猫警长",
		Gender: "男",
		Age:    18,
	}
	// 使用数据user渲染模板，并将结果写入w
	tmpl.Execute(w, user)
}

func tmplDemo(w http.ResponseWriter, r *http.Request) {

	// 自定义一个夸人的模板函数
	kua := func(arg string) (string, error) {
		return arg + ", 你是个好人", nil
	}

	// 定义一个用于渲染的数据
	user := UserInfo{
		Name:   "小王子",
		Gender: "男",
		Age:    18,
	}

	// 定义一个空模板并绑定方法并解析模板
	// tmpl, err := template.ParseFiles("./temp.tmpl", "./ul.tmpl")
	tmpl, err := template.New("temp.tmpl").Funcs(template.FuncMap{ // 在New中的参数必须是ParseFiles其中一个同名
		"kua": kua,
	}).ParseFiles("./temp.tmpl", "./ul.tmpl") // 被嵌套的放在后面
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}

	if err := tmpl.Execute(w, user); err != nil {
		fmt.Println(err)
	}
}

func index(w http.ResponseWriter, r *http.Request) {
	tpl, err := template.ParseGlob("templates/*.tpl")
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}
	tmp := map[string]string{
		"Title":   "go | template",
		"Content": "hello world",
	}
	err = tpl.ExecuteTemplate(w, "index.tpl", tmp)
	if err != nil {
		fmt.Println("render template failed, err:", err)
		return
	}
}

func main() {
	http.HandleFunc("/tmpl", tmplDemo)
	http.HandleFunc("/index", index)
	http.HandleFunc("/", sayHello)
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		fmt.Println("HTTP server failed, err:", err)
		return
	}
}
